﻿using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Plugin.Misc.Reservations.Models;
using Nop.Services.Catalog;
using Nop.Services.Events;
using System;
using System.Linq;
using System.Collections.Generic;

namespace Nop.Plugin.Misc.Reservations.Services 
{
    public class ReservationService : IReservationService 
    {

        #region Fields

        private readonly IReservationBusiness _reservationBusiness;
        private readonly IProductService _productService;
        private readonly IReservationWorkContext _reservationWorkContext;
        private readonly IEventPublisher _eventPublisher;

        #endregion

        #region Ctor

        public ReservationService(IReservationBusiness reservationBusiness,
            IProductService productService, 
            IReservationWorkContext reservationWorkContext,
            IEventPublisher eventPublisher)
        {
            _reservationBusiness = reservationBusiness;
            _productService = productService;
            _reservationWorkContext = reservationWorkContext;
            _eventPublisher = eventPublisher;
        }

        #endregion

        #region Implemented Methods

        public virtual ReservationViewModel ReservationDaily(DateTime selectedDate, int productId, int quantity)
        {
            Product product = _productService.GetProductById(productId);
            if (product == null)
            {
                throw new ArgumentNullException("product ID");
            }
            Customer currentCustomer = _reservationWorkContext.CurrentCustomer;
            IList<Reservation> bookedReservations = _reservationBusiness.BookedReservationsByDate(selectedDate);
            IList<Product> temporaryAdditionalProd = GetTemporaryFeaturedProducts();
            ReservationViewModel result = new ReservationViewModel
            {
                CurrentCustomer = currentCustomer,
                SelectedDate = selectedDate,
                SelectedProduct = product,
                Quantity = quantity <= 0 ? 1 : quantity,
                BookedReservations = bookedReservations,
                FeaturedAdditionalProducts = temporaryAdditionalProd,
            };
            return result;
        }

        public virtual ReservationViewModel SelectedSlot(DateTime selectedDate, int productId, int tableIndex, bool isCancelled)
        {
            ReservationViewModel model = new ReservationViewModel();
            int currentCustomerId = _reservationWorkContext.CurrentCustomer.Id;
            if (!CheckAndUpdateSelectedReservation(currentCustomerId, selectedDate, tableIndex, isCancelled, model.StatusMessage))
            {
                TryAddAndCheckConflictReservation(currentCustomerId, selectedDate, productId, tableIndex, isCancelled, model.StatusMessage);
            }
            model.BookedReservations = _reservationBusiness.BookedReservationsByDate(selectedDate);

            return model;
        }

        public virtual ReservationViewModel AddToCart(IDictionary<int, int> selectedItems)
        {
            ReservationViewModel model = new ReservationViewModel();
            IDictionary<int, int> selectedMainProducts = SelectedMainProducts();
            if (selectedMainProducts.Count == 0)
            {
                model.StatusMessage.Add((int)StatusMessageType.Warning, "Please select a reservation");

                return model;
            }
            IDictionary<int, int> addOnItems = RemoveIfQuantityIsZero(selectedItems);
            IDictionary<int, int> products = Merge(selectedMainProducts, addOnItems);
            foreach (KeyValuePair<int, int> pair in products)
            {
                if (!_reservationWorkContext.AddItemToCart(pair.Key, pair.Value))
                {
                    model.StatusMessage.Add((int)StatusMessageType.Error, "Some products cannot be added to cart");

                    return model;
                }
            }
            return model;
        }

        public virtual ReservationViewModel UpdatedBookedReservations(DateTime selectedDate)
        {
            ReservationViewModel model = new ReservationViewModel
            {
                BookedReservations = _reservationBusiness.BookedReservationsByDate(selectedDate),
            };
            return model;
        }

        public virtual IDictionary<int, int> StringToIntDictionary(IDictionary<string, string> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items");
            }
            IDictionary<int, int> result = new Dictionary<int, int>();
            foreach (KeyValuePair<string, string> pair in items)
            {
                int key = 0;
                int val = 0;
                if ((int.TryParse(pair.Key, out key) && key != 0)
                    && int.TryParse(pair.Value, out val))
                {
                    result.Add(key, val);
                }
            }
            return result;
        }

        #endregion

        private bool CheckAndUpdateSelectedReservation(int currentCustomerId, DateTime selectedDate, int tableIndex, bool isCancelled, IDictionary<int, string> statusMessage)
        {
            bool result = false;
            Reservation currentCustomerReservation =
                _reservationBusiness.CurrentReservation(currentCustomerId, selectedDate, tableIndex);
            if (currentCustomerReservation != null
                && isCancelled
                && currentCustomerReservation.TableIndex == tableIndex)
            {
                _reservationBusiness.Remove(currentCustomerReservation);
                statusMessage.Add((int)StatusMessageType.Success, "Reservation successfully cancelled");
                result = true;
            }
            return result;
        }

        private void TryAddAndCheckConflictReservation(int currentCustomerId, DateTime selectedDate, int productId, int tableIndex, bool isCancelled, IDictionary<int, string> statusMessage)
        {
            Reservation currentCustomerReservation =
                _reservationBusiness.CheckReservationConflict(selectedDate, tableIndex);
            if (currentCustomerReservation == null)
            {
                currentCustomerReservation = new Reservation
                {
                    ReserveDate = selectedDate,
                    CustomerID = currentCustomerId,
                    ProductID = productId,
                    Duration = 60,
                    TableIndex = tableIndex,
                    NumberPersons = 1,
                    NotificationType = 1,
                    NotificationTime = DateTime.MinValue,
                    NotificationEmailAddress = "",
                    NotificationSMSAddress = "",
                    SMSCarrier = 0,
                    Status = (int)ReservationStatusType.OnHold,
                    StoreID = 1,
                    Deleted = isCancelled,
                    Comments = "",
                };
                _reservationBusiness.Insert(currentCustomerReservation);
                statusMessage.Add((int)StatusMessageType.Success, 
                    string.Format("Time slot {0} reserved", currentCustomerReservation.ReserveDate));
            }
            else
            {
                statusMessage.Add((int)StatusMessageType.Other, 
                    string.Format("Time slot {0} has already been reserved", selectedDate));
            }
        }

        // Key: Product ID, Value: Quantity
        private IDictionary<int, int> SelectedMainProducts()
        {
            int currentCustomerId = _reservationWorkContext.CurrentCustomer.Id;
            IDictionary<int, int> selectedMainProducts = new Dictionary<int, int>();
            IList<Reservation> currentReservations = _reservationBusiness
                .CurrentReservationsByCustomerId(currentCustomerId);
            foreach (Reservation reservation in currentReservations)
            {
                if (selectedMainProducts.ContainsKey(reservation.ProductID))
                {
                    ++selectedMainProducts[reservation.ProductID];
                }
                else
                {
                    selectedMainProducts.Add(reservation.ProductID, 1);
                }
            }
            return selectedMainProducts;
        }

        private IList<Product> GetTemporaryFeaturedProducts()
        {
            IList<Product> products = _productService.GetAllProducts();
            IList<Product> featuredProducts = new List<Product>();
            featuredProducts.Add(products.Where(p => p.Name == "Rent A Rifle").FirstOrDefault());
            featuredProducts.Add(products.Where(p => p.Name == "Rent Eye and Ear Protection").FirstOrDefault());

            return featuredProducts;
        }

        private IDictionary<int, int> RemoveIfQuantityIsZero(IDictionary<int, int> items)
        {
            return items.Where(kvp => kvp.Value > 0).ToDictionary(i => i.Key, i => i.Value);
        }

        private IDictionary<int, int> Merge(IDictionary<int, int> d1, IDictionary<int, int> d2)
        {
            return d1.Concat(d2).GroupBy(k => k.Key).ToDictionary(k => k.Key, k => k.FirstOrDefault().Value);
        }
    }
}
